/**
 * Copyright 2013 yangming.liu<liuyangming@gmail.com>.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, see <http://www.gnu.org/licenses/>.
 */
package org.bytesoft.openjta.resource;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.transaction.HeuristicCommitException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;

import org.bytesoft.openjta.TransactionImpl;
import org.bytesoft.openjta.archive.XAResourceArchive;
import org.bytesoft.openjta.internal.RollbackRequiredException;
import org.bytesoft.openjta.internal.XAResourceWrapper;
import org.bytesoft.openjta.xa.XidImpl;
import org.bytesoft.utils.ByteUtils;

public class LocalTerminator extends AbstractLocalTerminator implements Terminator {
	private XAResourceArchive nonxaResourceArchive;
	private XAResourceArchive lastXAResourceArchive;

	private TransactionImpl transaction;

	public LocalTerminator(TransactionImpl transaction) {
		this.transaction = transaction;
	}

	public void commit(boolean transferCoordinate) throws HeuristicMixedException, HeuristicRollbackException,
			RollbackException, SecurityException, SystemException, RemoteException {
		throw new RuntimeException();
	}

	public void rollback(boolean transferCoordinate) throws HeuristicMixedException, HeuristicCommitException,
			SystemException, RemoteException {
		throw new RuntimeException();
	}

	public void lastResourceCommit() throws HeuristicMixedException, HeuristicRollbackException, SystemException {
		boolean committed = false;
		boolean rolledback = false;
		boolean mixedExists = false;

		XAResourceArchive archive = this.lastXAResourceArchive;
		if (archive.isCommitSuccess()) {
			committed = true;
		} else if (archive.isCommitFailure()) {
			if (archive.isHeuristicExists()) {
				int errcode = archive.getHeuristicCode();
				if (errcode == XAException.XA_HEURCOM) {
					committed = true;
				} else if (errcode == XAException.XA_HEURRB) {
					rolledback = true;
				} else if (errcode == XAException.XA_HEURHAZ) {
					mixedExists = true;
				} else if (errcode == XAException.XA_HEURMIX) {
					mixedExists = true;
				}
			}
		}

		if (committed) {
			// ignore
		} else if (rolledback) {
			throw new HeuristicRollbackException();
		} else if (mixedExists) {
			throw new HeuristicMixedException();
		} else {
			boolean success = false;
			try {
				XAResourceWrapper wrapper = archive.getXAResource();
				XidImpl branchXid = archive.getBranchXid();

				System.out.printf(String.format("\t[%s] commit: xares= %s%n",
						ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), wrapper));

				archive.setCommitStarted(true);
				transactionLogger.enterCommitXAResource(archive);

				/* one phase commit */
				wrapper.commit(branchXid, true);

				success = true;
			} catch (XAException xae) {
				xae.printStackTrace();

				// Possible XAExceptions are XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX
				// , XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
				int errcode = xae.errorCode;
				if (XAException.XA_HEURCOM == errcode) {
					success = true;
				} else if (XAException.XA_RBBASE <= errcode && errcode <= XAException.XA_RBEND) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURRB);

					throw new HeuristicRollbackException();
				} else if (XAException.XA_HEURMIX == errcode) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURMIX);

					throw new HeuristicMixedException();
				} else if (XAException.XA_HEURHAZ == errcode) {
					archive.setHeuristicExists(true);
					archive.setHeuristicCode(XAException.XA_HEURHAZ);

					throw new HeuristicMixedException();
				}

			} catch (RuntimeException rex) {
				rex.printStackTrace();

				SystemException exception = new SystemException();
				exception.initCause(rex);
				throw exception;
			} finally {
				archive.setCommitResult(success);
				transactionLogger.completeCommitXAResource(archive);
			}
		}

	}

	public synchronized boolean delistXAResource(XAResourceWrapper xares, int flags) throws IllegalStateException,
			SystemException, RollbackRequiredException {
		XAResourceArchive archive = null;
		Iterator<XAResourceArchive> itr = this.allXAResourceArchives.iterator();
		while (itr.hasNext()) {
			XAResourceArchive existedHolder = itr.next();
			XAResource existedResource = existedHolder.getXAResource();
			boolean isSameRM = false;
			if (existedResource.equals(xares)) {
				isSameRM = true;
			} else {
				try {
					isSameRM = existedResource.isSameRM(xares);
				} catch (XAException ex) {
					ex.printStackTrace();
				} catch (RuntimeException ex) {
					ex.printStackTrace();
				}
			}
			if (isSameRM) {
				archive = existedHolder;
				break;
			}
		}

		if (archive == null) {
			return false;
		} else {
			return this.delistXAResource(archive, flags);
		}
	}

	private synchronized boolean delistXAResource(XAResourceArchive archive, int flags) throws SystemException,
			RollbackRequiredException {
		try {
			XAResource xaRes = archive.getXAResource();
			XidImpl branchXid = archive.getBranchXid();
			System.out.printf(String.format("\t[%s] delist: xares= %s, flags= %s%n",
					ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xaRes, flags));

			xaRes.end(branchXid, flags);

			if (this.enlistedXAResourceArchives.contains(archive)) {
				this.delistedXAResourceArchives.add(archive);
				this.enlistedXAResourceArchives.remove(archive);
			} else if (this.delistedXAResourceArchives.contains(archive)) {
				// do nothing
			} else {
				throw new SystemException();
			}

			if (flags == XAResource.TMFAIL) {
				throw new RollbackRequiredException();
			} else {
				return true;
			}
		} catch (XAException xae) {
			xae.printStackTrace();

			// Possible XAException values are XAER_RMERR, XAER_RMFAIL,
			// XAER_NOTA, XAER_INVAL, XAER_PROTO, or XA_RB*.
			switch (xae.errorCode) {
			case XAException.XAER_RMFAIL:
			case XAException.XAER_NOTA:
			case XAException.XAER_INVAL:
			case XAException.XAER_PROTO:
				return false;
			case XAException.XAER_RMERR:
				SystemException sysex = new SystemException();
				sysex.initCause(xae);
				throw sysex;
			default:
				throw new RollbackRequiredException();
			}
		} catch (RuntimeException ex) {
			ex.printStackTrace();
			return false;
		}
	}

	public synchronized boolean delistNonXAResource(XAResourceWrapper xares, int flags) throws IllegalStateException,
			SystemException, RollbackRequiredException {
		if (this.nonxaResourceArchive == null) {
			return false;
		} else {
			return this.delistXAResource(this.nonxaResourceArchive, flags);
		}
	}

	public synchronized boolean delistNonXAResource(XAResourceArchive archive, int flags) throws SystemException,
			RollbackRequiredException {
		try {
			XAResource xaRes = archive.getXAResource();
			XidImpl branchXid = archive.getBranchXid();
			System.out.printf(String.format("\t[%s] delist: xares= %s, flags= %s%n",
					ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xaRes, flags));

			xaRes.end(branchXid, flags);

			if (this.enlistedXAResourceArchives.contains(archive)) {
				this.delistedXAResourceArchives.add(archive);
				this.enlistedXAResourceArchives.remove(archive);
			} else if (this.delistedXAResourceArchives.contains(archive)) {
				// do nothing
			} else {
				throw new SystemException();
			}

			if (flags == XAResource.TMFAIL) {
				throw new RollbackRequiredException();
			} else {
				return true;
			}
		} catch (XAException xae) {
			xae.printStackTrace();

			// Possible XAException values are XAER_RMERR, XAER_RMFAIL,
			// XAER_NOTA, XAER_INVAL, XAER_PROTO, or XA_RB*.
			switch (xae.errorCode) {
			case XAException.XAER_RMFAIL:
			case XAException.XAER_NOTA:
			case XAException.XAER_INVAL:
			case XAException.XAER_PROTO:
				return false;
			case XAException.XAER_RMERR:
				SystemException sysex = new SystemException();
				sysex.initCause(xae);
				throw sysex;
			default:
				throw new RollbackRequiredException();
			}
		} catch (RuntimeException ex) {
			ex.printStackTrace();
			return false;
		}
	}

	public synchronized boolean enlistXAResource(XAResourceWrapper xares) throws IllegalStateException,
			RollbackException, SystemException {
		XAResourceArchive archive = null;
		Iterator<XAResourceArchive> itr = this.allXAResourceArchives.iterator();
		while (itr.hasNext()) {
			XAResourceArchive existedHolder = itr.next();
			boolean isSameRM = false;
			if (existedHolder.getXAResource().equals(xares)) {
				isSameRM = true;
			} else {
				try {
					isSameRM = existedHolder.getXAResource().isSameRM(xares);
				} catch (XAException ex) {
					ex.printStackTrace();
				} catch (RuntimeException ex) {
					ex.printStackTrace();
				}
			}

			if (isSameRM) {
				archive = existedHolder;
				break;
			}
		}
		int flags = XAResource.TMNOFLAGS;
		if (archive == null) {
			archive = new XAResourceArchive();
			archive.setXaSupport(true);
			archive.setXAResource(xares);
			archive.setBranchXid(this.transaction.createBranchXid());
		} else {
			flags = XAResource.TMJOIN;
		}

		try {
			XAResourceWrapper xaRes = archive.getXAResource();
			XidImpl branchXid = archive.getBranchXid();
			System.out.printf(String.format("\t[%s] enlist: xares= %s, flags: %s%n",
					ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xaRes, flags));

			xaRes.start(branchXid, flags);
			if (flags == XAResource.TMNOFLAGS) {
				this.allXAResourceArchives.add(archive);
				this.enlistedXAResourceArchives.add(archive);
				this.calculateLastResourceArchive(archive);
			} else if (flags == XAResource.TMJOIN) {
				if (this.enlistedXAResourceArchives.contains(archive)) {
					// do nothing
				} else if (this.delistedXAResourceArchives.contains(archive)) {
					this.delistedXAResourceArchives.remove(archive);
					this.enlistedXAResourceArchives.add(archive);
				} else {
					throw new SystemException();
				}
			} else {
				throw new SystemException();
			}
			return true;
		} catch (XAException xae) {
			xae.printStackTrace();

			// Possible exceptions are XA_RB*, XAER_RMERR, XAER_RMFAIL,
			// XAER_DUPID, XAER_OUTSIDE, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
			switch (xae.errorCode) {
			case XAException.XAER_RMFAIL:
			case XAException.XAER_DUPID:
			case XAException.XAER_OUTSIDE:
			case XAException.XAER_NOTA:
			case XAException.XAER_INVAL:
			case XAException.XAER_PROTO:
				return false;
			case XAException.XAER_RMERR:
				SystemException sysex = new SystemException();
				sysex.initCause(xae);
				throw sysex;
			default:
				throw new RollbackException();
			}
		} catch (RuntimeException ex) {
			ex.printStackTrace();
			throw new RollbackException();
		}
	}

	public synchronized boolean enlistNonXAResource(XAResourceWrapper xares) throws IllegalStateException,
			RollbackException, SystemException {
		try {
			if (this.nonxaResourceArchive == null) {
				int flags = XAResource.TMNOFLAGS;
				XAResourceArchive archive = new XAResourceArchive();
				archive.setXaSupport(false);
				archive.setXAResource(xares);
				archive.setBranchXid(this.transaction.createBranchXid());

				XidImpl branchXid = archive.getBranchXid();
				System.out.printf(String.format("\t[%s] enlist: xares= %s, flags: %s%n",
						ByteUtils.byteArrayToString(branchXid.getBranchQualifier()), xares, flags));
				this.calculateLastResourceArchive(archive);
				xares.start(branchXid, flags);
			} else if (this.nonxaResourceArchive.getXAResource().equals(xares)) {
				// ignore
			} else {
				throw new SystemException();
			}
			return true;
		} catch (XAException xae) {
			xae.printStackTrace();

			// Possible exceptions are XA_RB*, XAER_RMERR, XAER_RMFAIL,
			// XAER_DUPID, XAER_OUTSIDE, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
			switch (xae.errorCode) {
			case XAException.XAER_RMFAIL:
			case XAException.XAER_DUPID:
			case XAException.XAER_OUTSIDE:
			case XAException.XAER_NOTA:
			case XAException.XAER_INVAL:
			case XAException.XAER_PROTO:
				return false;
			case XAException.XAER_RMERR:
				SystemException sysex = new SystemException();
				sysex.initCause(xae);
				throw sysex;
			default:
				throw new RollbackException();
			}
		} catch (RuntimeException ex) {
			throw new RollbackException();
		}
	}

	private void calculateLastResourceArchive(XAResourceArchive archive) {
		if (archive == null) {
			return;
		} else if (this.nonxaResourceArchive != null) {
			return;
		}

		XAResourceArchive original = this.lastXAResourceArchive;
		if (archive.isXaSupport()) {
			this.lastXAResourceArchive = archive;
		} else {
			this.nonxaResourceArchive = archive;
			this.lastXAResourceArchive = archive;
		}

		if (original != null) {
			original.setLastResource(false);
		}
		this.lastXAResourceArchive.setLastResource(true);
	}

	public synchronized void delistAllLocalResources() throws RollbackRequiredException {
		boolean rollbackRequired = false;
		List<XAResourceArchive> archives = new ArrayList<XAResourceArchive>(this.enlistedXAResourceArchives);
		Iterator<XAResourceArchive> enlistedItr = archives.iterator();
		while (enlistedItr.hasNext()) {
			XAResourceArchive archive = enlistedItr.next();
			try {
				if (archive.isXaSupport()) {
					this.delistXAResource(archive, XAResource.TMSUCCESS);
				} else {
					this.delistNonXAResource(archive, XAResource.TMSUCCESS);
				}
			} catch (SystemException ex) {
				// rollbackRequired = true;
				ex.printStackTrace();
			} catch (RollbackRequiredException ex) {
				rollbackRequired = true;
				ex.printStackTrace();
			} catch (RuntimeException ex) {
				// rollbackRequired = true;
				ex.printStackTrace();
			}
		}

		if (rollbackRequired) {
			throw new RollbackRequiredException();
		}
	}

	public synchronized void resumeAllResource() throws IllegalStateException, SystemException,
			RollbackRequiredException {
		boolean errorExists = false;
		boolean rollbackRequired = false;
		Iterator<XAResourceArchive> itr = this.suspendedXAResourceArchives.iterator();
		while (itr.hasNext()) {
			XAResourceArchive archive = itr.next();
			this.enlistedXAResourceArchives.add(archive);
			itr.remove();
			try {
				XAResource xaRes = archive.getXAResource();
				XidImpl branchXid = archive.getBranchXid();
				xaRes.start(branchXid, XAResource.TMRESUME);
			} catch (XAException xae) {
				xae.printStackTrace();

				// Possible exceptions are XA_RB*, XAER_RMERR, XAER_RMFAIL,
				// XAER_DUPID, XAER_OUTSIDE, XAER_NOTA, XAER_INVAL, or
				// XAER_PROTO.
				switch (xae.errorCode) {
				case XAException.XAER_RMFAIL:
				case XAException.XAER_DUPID:
				case XAException.XAER_OUTSIDE:
				case XAException.XAER_NOTA:
				case XAException.XAER_INVAL:
				case XAException.XAER_PROTO:
					break;
				case XAException.XAER_RMERR:

					errorExists = true;
					break;
				default:
					rollbackRequired = true;
				}
			} catch (RuntimeException ex) {
				ex.printStackTrace();
			}
		}
		if (rollbackRequired) {
			throw new RollbackRequiredException();
		} else if (errorExists) {
			throw new SystemException();
		}
	}

	public synchronized void suspendAllResource() throws IllegalStateException, SystemException,
			RollbackRequiredException {
		boolean rollbackRequired = false;
		boolean errorExists = false;
		Iterator<XAResourceArchive> itr = this.enlistedXAResourceArchives.iterator();
		while (itr.hasNext()) {
			XAResourceArchive archive = itr.next();
			this.suspendedXAResourceArchives.add(archive);
			itr.remove();
			try {
				XAResource xaRes = archive.getXAResource();
				XidImpl branchXid = archive.getBranchXid();
				xaRes.end(branchXid, XAResource.TMSUSPEND);
			} catch (XAException xae) {
				xae.printStackTrace();
				// Possible XAException values are XAER_RMERR, XAER_RMFAIL,
				// XAER_NOTA, XAER_INVAL, XAER_PROTO, or XA_RB*.
				switch (xae.errorCode) {
				case XAException.XAER_RMFAIL:
				case XAException.XAER_NOTA:
				case XAException.XAER_INVAL:
				case XAException.XAER_PROTO:
					break;
				case XAException.XAER_RMERR:
					errorExists = true;
					break;
				default:
					rollbackRequired = true;
				}
			} catch (RuntimeException ex) {
				ex.printStackTrace();
			}
		}
		if (rollbackRequired) {
			throw new RollbackRequiredException();
		} else if (errorExists) {
			throw new SystemException();
		}
	}

	public void cleanup() throws SystemException, RemoteException {
		throw new IllegalStateException();
	}

	// public int ready() throws RemoteException {
	// return Terminator.READY_OK;
	// }

	public int getStatus() throws RemoteException {
		throw new IllegalStateException();
	}

	public XidImpl getXid() {
		throw new IllegalStateException();
	}

	public XAResourceArchive getLastXAResourceArchive() {
		return lastXAResourceArchive;
	}

	public void setLastXAResourceArchive(XAResourceArchive lastXAResourceArchive) {
		this.lastXAResourceArchive = lastXAResourceArchive;
	}

}
